package com.example.judge.config;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import com.example.common.core.constants.JudgeConstants;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ListImagesCmd;
import com.github.dockerjava.api.command.PullImageCmd;
import com.github.dockerjava.api.command.PullImageResultCallback;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.api.model.Volume;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

@Slf4j
public class DockerSandBoxPool {
    //Docker客户端对象，用于与Docker守护进程通信
    private DockerClient dockerClient;
    //Docker镜像名称，用于创建容器
    private String sandboxImage;
    //挂载目录，用于存储用户代码文件
    private String volumeDir;
    //容器的内存限制
    private Long memoryLimit;
    //交换内存
    private Long memorySwapLimit;
    //CPU核心数
    private Long cpuLimit;
    //容器池的大小，即池中容器的数量
    private int poolSize;
    //容器名称前缀，用于生成唯一的容器名称
    private String containerNamePrefix;
    //阻塞队列，用于管理容器ID的复用
    private BlockingQueue<String> containerQueue;
    //映射表，用于存储容器ID和容器名称的对应关系。
    private Map<String, String> containerNameMap;

    public DockerSandBoxPool(DockerClient dockerClient,
                             String sandboxImage,
                             String volumeDir, Long memoryLimit,
                             Long memorySwapLimit, Long cpuLimit,
                             int poolSize, String containerNamePrefix) {
        this.dockerClient = dockerClient;
        this.sandboxImage = sandboxImage;
        this.volumeDir = volumeDir;
        this.memoryLimit = memoryLimit;
        this.memorySwapLimit = memorySwapLimit;
        this.cpuLimit = cpuLimit;
        this.poolSize = poolSize;
        this.containerQueue = new ArrayBlockingQueue<>(poolSize);
        this.containerNamePrefix = containerNamePrefix;
        this.containerNameMap = new HashMap<>();
    }

    public void initDockerPool() {  //初始化容器池的
        log.info("------  创建容器开始  -----");
        for(int i = 0; i < poolSize; i++) {
            createContainer(containerNamePrefix + "-" + i);
        }
        log.info("------  创建容器结束  -----");
    }


    //从阻塞队列中获取一个容器ID。如果队列为空，线程会阻塞直到有容器可用
    public String getContainer() {
        try {
            return containerQueue.take();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public void returnContainer(String containerId) {
        containerQueue.add(containerId);
    }

    private void createContainer(String containerName) {
        //如果该容器已经存在，则直接启动并存入队列和映射表中，并返回
        System.out.println("12313");
        List<Container> containerList = dockerClient.
                //创建一个列出所有容器的命令。
                listContainersCmd()
                //设置命令以显示所有容器，包括已停止的容器。
                .withShowAll(true).
                //执行命令
                exec();
        //检查容器列表是否为空。如果列表不为空，继续执行后续逻辑。
        if (!CollectionUtil.isEmpty(containerList)) {
            //构建容器名称
            String names = JudgeConstants.JAVA_CONTAINER_PREFIX + containerName;
            //遍历每个容器对象。
            for (Container container : containerList) {
                //获取容器的名称数组。一个容器可以有多个名称，但通常第一个名称是主要名称。
                String[] containerNames = container.getNames();
                //检查容器名称是否匹配
                if (containerNames != null && containerNames.length > 0 && names.equals(containerNames[0])) {
                    //检查容器状态并启动
                    if ("created".equals(container.getState()) || "exited".equals(container.getState())) {
                        //启动容器
                        dockerClient.startContainerCmd(container.getId()).exec();
                    }
                    containerQueue.add(container.getId());
                    containerNameMap.put(container.getId(), containerName);
                    return;
                }
            }
        }

        //如果该容器已经存在，则直接启动并存入队列和映射表中，并返回
        //拉取镜像
        System.out.println("+++++++++++++++++");
        pullJavaEnvImage();
        //创建容器  限制资源   控制权限
        // 获取容器配置，这个方法返回一个 HostConfig 对象，该对象包含容器的配置信息。
        HostConfig hostConfig = getHostConfig(containerName);

        CreateContainerCmd containerCmd = dockerClient
                //创建一个创建容器的命令，指定使用的镜像，这里是jdk8
                .createContainerCmd(JudgeConstants.JAVA_ENV_IMAGE)
                //设置容器的名称
                .withName(containerName);
        //配置容器
        CreateContainerResponse createContainerResponse = containerCmd
                //设置容器的配置，包括资源限制和挂载卷
                .withHostConfig(hostConfig)
                //附加标准错误流，以便捕获容器的错误输出
                .withAttachStderr(true)
                //附加标准输出流，以便捕获容器的正常输出
                .withAttachStdout(true)
                //启用 TTY 模式，允许容器在前台运行
                .withTty(true)
                //执行创建容器的命令
                .exec();
        //记录容器id
        String containerId = createContainerResponse.getId();
        //启动容器
        dockerClient.startContainerCmd(containerId).exec();
        //加入队列中
        containerQueue.add(containerId);
        //加入映射列表中
        containerNameMap.put(containerId, containerName);
    }

    private void pullJavaEnvImage() {
        // 列出所有镜像
        ListImagesCmd listImagesCmd = dockerClient.listImagesCmd();
        List<Image> imageList = listImagesCmd.exec();
        //遍历镜像
        for (Image image : imageList) {
            //获取镜像的标签数组。一个镜像可以有多个标签，但通常第一个标签是主要标签。
            String[] repoTags = image.getRepoTags();
            //检查第一个标签是否与所需的镜像名称匹配。
            if (repoTags != null && repoTags.length > 0 && sandboxImage.equals(repoTags[0])) {
                return;
            }
        }
        System.out.println("=--------------");
        //拉取镜像
        PullImageCmd pullImageCmd = dockerClient.pullImageCmd(sandboxImage);

        try {
            System.out.println(pullImageCmd.getTag());
            pullImageCmd.exec(new PullImageResultCallback()).awaitCompletion();
            System.out.println("拉取镜像成");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    //限制资源   控制权限
    private HostConfig getHostConfig(String containerName) {
        HostConfig hostConfig = new HostConfig();
        //设置挂载目录，指定用户代码路径
        String userCodeDir = createContainerDir(containerName);
        //设置挂载绑定，将本地目录 userCodeDir 挂载到容器内的 volumeDir 路径
        hostConfig.setBinds(new Bind(userCodeDir, new Volume(volumeDir)));
        //限制docker容器使用资源
        hostConfig.withMemory(memoryLimit);
        hostConfig.withMemorySwap(memorySwapLimit);
        hostConfig.withCpuCount(cpuLimit);
        hostConfig.withNetworkMode("none");  //禁用网络
        hostConfig.withReadonlyRootfs(true); //禁止在root目录写文件
        return hostConfig;
    }

    public String getCodeDir(String containerId) {
        //获取容器名称
        String containerName = containerNameMap.get(containerId);
        log.info("containerName：{}", containerName);

        return System.getProperty("user.dir") + File.separator + JudgeConstants.CODE_DIR_POOL + File.separator + containerName;
    }

    //为每个容器，创建的指定挂载文件
    private String createContainerDir(String containerName) {
        //一级目录  存放所有容器的挂载目录
        String codeDir = System.getProperty("user.dir") + File.separator + JudgeConstants.CODE_DIR_POOL;
        if (!FileUtil.exist(codeDir)) {
            FileUtil.mkdir(codeDir);
        }
        return codeDir + File.separator + containerName;
    }
}
